﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;

namespace ConquerServer_Basic
{
    public enum PKMode : byte
    {
        PK = 0,
        Peace = 1,
        Team = 2,
        Capture = 3
    }

    public class GameClient : AuthClient
    {
        public PKMode PKMode;

        public int ConquerPoints;
        public int Money;

        public ushort Agility;
        public ushort StatPoints;
        public ushort Strength;
        public ushort Vitality;
        public ushort Spirit;

        public ushort Mana;
        public ushort MaxMana;

        public PlayerTeam Team; // null if no team is active
        public Entity Entity;
        public Screen Screen;
        private Dictionary<uint, IConquerItem> m_Inventory;
        public IConquerItem[] Inventory;

        public Dictionary<ushort, IConquerItem> Equipment;

        public Dictionary<ushort, ISkill> Profs, Spells;

        public byte Job;
        public string Spouse;

        // These should only be modified in Database.(Un)LoadItemStats ----
        public uint BaseMinAttack;
        public uint BaseMaxAttack;
        public uint BaseMagicAttack;
        public ushort ItemHP;
        public ushort ItemMP;
        // -----

        private ushort StatHP;
        public byte BlessPercent;
        public sbyte AttackRange;
        public uint AttackSpeed;
        public short[] Gems;
        public sbyte Stamina;
        public uint ActiveNpcID;

        /* Returns false if already contained, true if it is added */
        public bool LearnProf(ISkill Prof)
        {
            if (Profs.ContainsKey(Prof.ID))
            {
                Profs[Prof.ID] = Prof;
                Prof.Send(this);
                return false;
            }
            else
            {
                this.Profs.Add(Prof.ID, Prof);
                Prof.Send(this);
                return true;
            }
        }
        public bool LearnProf(ushort ID, ushort Level)
        {
            ISkill Prof = new ProfPacket(true);
            Prof.ID = ID; Prof.Level = Level;
            return LearnProf(Prof);
        }
        public bool LearnSpell(ISkill Spell)
        {
            if (this.Spells.ContainsKey(Spell.ID))
            {
                Spells[Spell.ID] = Spell;
                Spell.Send(this);
                return false;
            }
            else
            {
                Spells.Add(Spell.ID, Spell);
                Spell.Send(this);
                return true;
            }
        }
        public bool LearnSpell(ushort ID, ushort Level)
        {
            ISkill Spell = new SpellPacket(true);
            Spell.ID = ID;
            Spell.Level = Level;
            return LearnSpell(Spell);
        }

        public GameClient(HybridWinsockClient _Socket) : base(_Socket)
        {
            this.Entity = new Entity(EntityFlag.Player, this);
            this.AuthPhase = AuthPhases.GameServer;
            this.Screen = new Screen(this);
            this.m_Inventory = new Dictionary<uint, IConquerItem>(40);
            this.Inventory = new IConquerItem[0];
            this.Equipment = new Dictionary<ushort, IConquerItem>(9);
            this.Gems = new short[8];

            this.Profs = new Dictionary<ushort, ISkill>();
            this.Spells = new Dictionary<ushort, ISkill>();
        }

        public void CalculateHPBonus()
        {
            switch (this.Job)
            {
                case 11: this.Entity.MaxHitpoints = (int)(this.StatHP * 1.05F); break;
                case 12: this.Entity.MaxHitpoints = (int)(this.StatHP * 1.08F); break;
                case 13: this.Entity.MaxHitpoints = (int)(this.StatHP * 1.10F); break;
                case 14: this.Entity.MaxHitpoints = (int)(this.StatHP * 1.12F); break;
                case 15: this.Entity.MaxHitpoints = (int)(this.StatHP * 1.15F); break;
                default: this.Entity.MaxHitpoints = (int)this.StatHP; break;
            }
            this.Entity.MaxHitpoints += this.ItemHP;
            this.Entity.Hitpoints = Math.Min(this.Entity.Hitpoints, this.Entity.MaxHitpoints);
        }
        public void CalculateDamageBoost()
        {
            this.Entity.MaxAttack = (uint)((this.Strength + this.BaseMaxAttack) * (1 + (this.Gems[1] * 0.01))); // dg
            this.Entity.MinAttack = (uint)((this.Strength + this.BaseMinAttack) * (1 + (this.Gems[1] * 0.01))); // dg
            this.Entity.MagicAttack = (uint)(this.BaseMagicAttack * (1 + (this.Gems[0] * 0.01))); // pg
        }
        public void CalculateStatBonus()
        {
            byte ManaBoost = 5;
            const byte HitpointBoost = 24;
            sbyte JobID = (sbyte)(this.Job / 10);
            if (JobID == 13 || JobID == 14)
                ManaBoost += (byte)(5 * (this.Job - (JobID * 10)));
            this.StatHP = (ushort)((this.Strength * 3) +
                                     (this.Agility * 3) +
                                     (this.Spirit * 3) +
                                     (this.Vitality * HitpointBoost) + 1);
            this.MaxMana = (ushort)((this.Spirit * ManaBoost) + this.ItemMP);
            this.Mana = Math.Min(this.Mana, this.MaxMana);
        }

        public void LoadEquipment()
        {
            lock (this.Equipment)
            {
                foreach (KeyValuePair<ushort, IConquerItem> DE in Equipment)
                {
                    LoadItemStats(DE.Value);
                    Send(DE.Value as IClassPacket);
                }
            }
        }
        public void LoadItemStats(IConquerItem Item)
        {
            IniFile rdr;
            StanderdItemStats standerd = new StanderdItemStats(Item.ID, out rdr);

            this.BaseMinAttack += standerd.MinAttack;
            this.BaseMaxAttack += standerd.MaxAttack;

            this.Entity.Defence += standerd.PhysicalDefence;
            this.Entity.MDefence += standerd.MDefence;
            this.Entity.Dodge += standerd.Dodge;

            this.BaseMagicAttack += standerd.MAttack;
            this.ItemHP += standerd.HP;
            this.ItemMP += standerd.MP;
            if (Item.Position == ItemPosition.Right)
            {
                this.AttackSpeed = standerd.Frequency;
                this.AttackRange += standerd.AttackRange;
            }

            if (Item.Plus != 0)
            {
                PlusItemStats plus = new PlusItemStats(Item.ID, Item.Plus, rdr);
                this.BaseMinAttack += plus.MinAttack;
                this.BaseMaxAttack += plus.MaxAttack;
                this.BaseMagicAttack += plus.MAttack;
                this.Entity.Defence += plus.PhysicalDefence;
                this.Entity.Dodge += plus.Dodge;
                this.Entity.PlusMDefence += plus.PlusMDefence;
                this.ItemHP += plus.HP;
            }
            if (Item.Position != ItemPosition.Garment && // Ignore these stats on these slots
                Item.Position != ItemPosition.Bottle)
            {
                this.ItemHP += Item.Enchant;
                this.BlessPercent += Item.Bless;
                //GemAlgorithm(this, Item.SocketOne, Item.SocketTwo, true);
            }
        }

        public void Teleport(ushort MapID, ushort X, ushort Y)
        {
            DataPacket Packet = new DataPacket(true);
            Packet.ID = DataPacket.RemoveEntity;
            Packet.UID = this.Entity.UID;
            this.SendScreen(Packet, false);

            this.Entity.MapID = MapID;
            this.Entity.X = X;
            this.Entity.Y = Y;

            Packet.ID = DataPacket.SetLocation;
            Packet.dwParam = this.Entity.MapID;
            Packet.wParam1 = this.Entity.X;
            Packet.wParam2 = this.Entity.Y;
            this.Send(Packet);
        }

        public void SendScreen(byte[] Msg, bool SendSelf)
        {
            foreach (IMapObject obj in Screen.Objects)
            {
                if (obj.MapObjType == MapObjectType.Player)
                {
                    (obj.Owner as GameClient).Send(Msg);
                }
            }
            if (SendSelf)
                Send(Msg);
        }
        public void SendScreen(IClassPacket CMsg, bool SendSelf)
        {
            SendScreen(CMsg.Serialize(), SendSelf);
        }

        private void CreateInventoryInstance()
        {
            IConquerItem[] temp = new IConquerItem[m_Inventory.Count];
            m_Inventory.Values.CopyTo(temp, 0);
            Inventory = temp;
        }   
        public bool AddInventory(IConquerItem Item)
        {
            lock (m_Inventory)
            {
                if (m_Inventory.Count < 40)
                {
                    Item.Position = ItemPosition.Inventory;
                    m_Inventory.Add(Item.UID, Item);
                    Item.Send(this);
                    CreateInventoryInstance();

                    return true;
                }
            }
            return false;
        }
        public void RemoveInventory(uint UID)
        {
            lock (m_Inventory)
            {
                m_Inventory.Remove(UID);
                ItemUsagePacket Remove = new ItemUsagePacket(true);
                Remove.ID = ItemUsagePacket.RemoveInventory;
                Remove.UID = UID;
                this.Send(Remove);
                CreateInventoryInstance();
            }
        }
        public sbyte CountInventory(uint ItemID)
        {
            sbyte Count = 0;
            foreach (IConquerItem item in Inventory)
                if (item.ID == ItemID)
                    Count++;
            return Count;
        }
        public IConquerItem GetInventoryItem(uint UID)
        {
            IConquerItem item;
            m_Inventory.TryGetValue(UID, out item);
            return item;
        }
        // This method should ONLY be called if you need
        // the instance to the items, not the count, if you want
        // the count for an itemID, you should call CountInventory()
        public IEnumerable<IConquerItem> SearchInventory(uint ItemID)
        {
            foreach (IConquerItem item in Inventory)
            {
                if (item.ID == ItemID)
                    yield return item;
            }
        }

        public void SendStatMessage()
        {
            MessagePacket Msg = new MessagePacket(" Attack: {0}~{1} MagicAttack: {2} Defence: {3} MDefence: {4} Dodge: {5}",
                0x00FFFFFF, MessagePacket.Center);
            Msg.Message = string.Format(Msg.Message,
                new object[] { this.Entity.MinAttack, this.Entity.MaxAttack, this.Entity.MagicAttack, this.Entity.Defence, (this.Entity.MDefence + this.Entity.PlusMDefence), this.Entity.Dodge });
            this.Send(Msg);
        }

        public bool Unequip(ushort EquipSlot)
        {
            IConquerItem Removed;
            if (Equipment.TryGetValue(EquipSlot, out Removed))
            {
                if (AddInventory(Removed))
                {
                    Equipment.Remove(EquipSlot);

                    ItemUsagePacket UnEquipPack = new ItemUsagePacket(true);
                    UnEquipPack.UID = Removed.UID;
                    UnEquipPack.ID = ItemUsagePacket.UnequipItem;
                    Send(UnEquipPack);

                    ushort swap = Removed.Position;
                    Removed.Position = EquipSlot;
                    Database.UnloadItemStats(this, Removed);
                    Removed.Position = swap;
                    return true;
                }
            }
            return false;
        }
        public bool Equip(IConquerItem Item, ushort EquipSlot)
        {
            if (!Equipment.ContainsKey(EquipSlot))
            {
                Item.Position = EquipSlot;
                Equipment.Add(EquipSlot, Item);
                Item.Send(this);
                Database.LoadItemStats(this, Item);
                return true;
            }
            return false;
        }
    }
}
